const common = require('./base/common');

const calSigns = {
    "+": {
        outside: 3, inside: 4, process: (stack) => {
            if (stack.length <= 0) {
                throw new Error("invalid params length in +");
            }
            let b = stack.length > 0 ? stack.pop() : 0,
                a = stack.length > 0 ? stack.pop() : 0;
            return a + b;
        }
    },
    "-": {
        outside: 5, inside: 6, process: (stack) => {
            if (stack.length <= 0) {
                throw new Error("invalid params length in -");
            }
            let b = stack.length > 0 ? stack.pop() : 0,
                a = stack.length > 0 ? stack.pop() : 0;
            return a - b;
        }
    },
    "*": {
        outside: 7, inside: 8, process: (stack) => {
            if (stack.length <= 1) {
                throw new Error("invalid params length in *");
            }
            let b = stack.pop(), a = stack.pop();
            return a * b;
        }
    },
    "/": {
        outside: 9, inside: 10, process: (stack) => {
            if (stack.length <= 1) {
                throw new Error(`invalid params length in /`);
            }
            let b = stack.pop(), a = stack.pop();
            return Math.floor(a / b);
        }
    },
    ".": {
        outside: 12, inside: 11, process: (stack) => {
            if (stack.length <= 0) {
                throw new Error(`invalid params length in @`);
            }
            let b = stack.length > 0 ? stack.pop() : 0,
                a = stack.length > 0 ? stack.pop() : 0;

            return parseInt(a) + (b / Math.pow(10, (b + "").length));
        }
    },
    "!": {
        outside: 100, inside: 101, process: (stack) => {
            if (stack.length <= 0) {
                throw new Error("invalid params length in !");
            }
            let c = stack.pop(), n = 1;
            if (c < 0) {
                throw new Error("invalid params in !");
            }
            for (let i = 1, len = Math.floor(c); i <= len; ++i) {
                n *= i;
            }
            return n;
        }
    },
    "^": {
        outside: 99, inside: 98, process: (stack) => {
            if (stack.length <= 1) {
                throw new Error("invalid params length in ^");
            }
            let b = stack.pop(), a = stack.pop();
            return Math.pow(a, b);
        }
    },
    "(": { outside: 999, inside: 2 },
    ")": { outside: 2, inside: 999, popOncePair: "(" },
    "#": { outside: 1, inside: 1000 }
}

/**
 * 224. 基本计算器
 * 227. 基本计算器 II
 * 
 * @param {string} s
 * @return {number}
 */
var calculate = function (s) {
    let calStack = [],
        opStack = [],
        token = null;

    // 弹出检查，左右括号只能弹出一层
    // 括号是一种层次运算，要与普通算符加以区分
    function popCheck(c) {
        if (opStack.length <= 0) {
            return false;
        }
        let top = opStack[opStack.length - 1];
        if (calSigns[c].popOncePair == top) {
            opStack.pop();
            return false;
        }
        if (calSigns[c].outside <= calSigns[top].inside) {
            return true;
        }
        return false;
    }

    // 判断是否为无效字符
    function invalidCharacter(c) {
        if (c == " ") {
            return true;
        }
        if (!calSigns.hasOwnProperty(c)) {
            throw new Error("invalid character");
        }
        return false;
    }

    // 计算栈
    function calculateInCalStack() {
        let op = calStack.pop();
        if (calSigns[op] && calSigns[op].process) {
            calStack.push(calSigns[op].process(calStack));
            return;
        }
    }

    // 操作队列
    function operateInOpQueue(c) {
        // 处理词法部分
        let code = c.charCodeAt();
        if (code >= 48 && code <= 57) {
            token = token ? token + c : c;
            return;
        } else if (token) {
            calStack.push(parseFloat(token));
            token = null;
        }

        // 操作栈计算（根据算符优先级进行计算）
        if (invalidCharacter(c)) {
            return;
        }
        while (popCheck(c)) {
            calStack.push(opStack.pop());
            calculateInCalStack();
        }
        opStack.push(c);
    }

    s = s + "#";
    for (let i = 0; i < s.length; ++i) {
        operateInOpQueue(s[i]);
    }

    return calStack.length != 1 ? NaN : calStack[0];
};

common.testing([
    "-2+ 1",
    "1 + 1",
    "(24 - 2) +  58 ",
    "(8+(4+5+2)-3)+(6+8)",
    " 2-1 + 2 ",
    "2-4-(8+2-6+(8+4-(1)+8-10))",
    "16  / (5 + 3)",
    "3+(2*2)",
    " 3/2 ",
    "5 * 2 - 3",
    "3 * 2!",
    "3 + 2 * 2 - 1",
    "(((1) *  2) - 3) + 4)",
    "3!!",
    ".6 + 1.2"
], [-1, 2, 80, 30, 3, -15, 2, 7, 1, 7, 6, 6, 3, 720], [0])((test) => calculate(test));